package view;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;

import common.Utils;

import model.DrawBezier;
import model.DrawEllipse;
import model.DrawLine;
import model.DrawObject;
import model.DrawRect;
import model.DrawText;

public abstract class ViewObject
{
	protected DrawObject dObj;
	protected Graphics2D g2;
	protected int sel_char_index = -1;
	
	public ViewObject(DrawObject obj, Graphics g)
	{
		this.g2 = (Graphics2D)g;
		dObj = obj;
	}
	
	public ViewObject(DrawObject obj, Graphics2D g2)
	{
		this.g2 = g2;
		dObj = obj;
	}
	

	public int distSqToShape(Point p)
	{
		Point nearP = nearestPoint(p, 0, 1);
		return (int)distSq(p, nearP);
	}
	
	public int distSqToShape(Point p, double t1, double t2)
	{
		Point nearP = nearestPoint(p, t1, t2);
		return (int)distSq(p, nearP);
	}
	
	
	private final static int N = 10;
	public Point nearestPoint(Point p, double lowerT, double upperT)
	{
		double inc = (upperT-lowerT)/N;
		Point lowP = computePoint(lowerT);
		Point highP = computePoint(upperT);
		
		if( distSq(lowP, highP) < 1.0 )
			return lowP;
		
		double nearT = lowerT;
		Point nearP = lowP;
		double nearD = distSq(nearP, p);
		
		double t_old = -1;
		for (double t=lowerT+inc; t<=upperT; t=t+inc)
		{
			if (t_old == t) 	// if the increment is so small that 't' effectively
				return lowP;	// does not change then we are finished 
			t_old = t;
			
			Point tp = computePoint(t);
			if (distSq(tp,p) < nearD)
			{
				nearD = distSq(tp,p);
				nearT = t;
				nearP = tp;
			}
		}
		
		double newLow = nearT - inc;
		if (newLow < lowerT) 
			newLow = lowerT;
		
		double newHi = nearT + inc;
		if (newHi > upperT) 
			newHi = upperT;
		
		return nearestPoint(p, newLow, newHi);
	}
	
	protected double distSq(Point a, Point b)
	{
		double dx = a.x - b.x;
		double dy = a.y - b.y;
		
		return dx*dx + dy*dy;
	}
	
	private Point computePoint(double t)
	{
		Point result = new Point();
		
		result.x = pFuncX(t);
		result.y = pFuncY(t);
		
		return result;
	}
	
	
	public int getSelCharIndex()
	{
		return sel_char_index;
	}
	
	public Rectangle getBounds()
	{
		BoundBox box = this.getBounds(true);
		return new Rectangle(box.getX(), box.getY(), box.getWidth(), box.getHeight());
	}
	
	public void DrawControlPoint(int x, int y, Graphics g)
	{	
		x = x-5;
		y = y-5;
		int width = 11;
		int height = 11;
		
		Graphics2D g2 = (Graphics2D)g;
		Color c = new Color(255, 255, 255);
		g2.setColor(c);
		g2.fillOval(x, y, width, height);
		
		c = new Color(0,0,0);
		g2.setColor(c);
		g2.setStroke( new BasicStroke(1));
		g2.drawOval(x, y, width, height);
	}
	
	 
	
	
	
	/*
	 * ******************************************
	 * 			Abstract/Override Functions
	 * ******************************************
	 * 
	 */

	public void Draw(Graphics g)
	{
		// comment/uncomment this code to show the bounding boxes around the shapes
		
//		Graphics2D g2 = (Graphics2D)g;
//		
//		BoundBox box = getBounds(true);
//		g2.setColor( new Color(0,0,0) );
//		g2.setStroke( new BasicStroke(1) );
//		g2.drawRect(box.getX(), box.getY(), box.getWidth(), box.getHeight());
	}
	
	public abstract int pFuncX(double t);

	public abstract int pFuncY(double t);

	public abstract BoundBox getBounds(boolean padded);
	
	public abstract boolean containsPoint(Point p);
	
	public void deltaTranslateObject(int dx, int dy)
	{
		this.dObj.setX1( dObj.getX1() + dx );
		this.dObj.setY1( dObj.getY1() + dy );
		
		this.dObj.setX2( dObj.getX2() + dx );
		this.dObj.setY2( dObj.getY2() + dy );
		
		this.dObj.setX3( dObj.getX3() + dx );
		this.dObj.setY3( dObj.getY3() + dy );
		
		this.dObj.setX4( dObj.getX4() + dx );
		this.dObj.setY4( dObj.getY4() + dy );
	}
	
	public void deltaTranslatePoint(int pointNo, int dx, int dy)
	{
		switch (pointNo)
		{
		case 1:
			this.dObj.setX1( dObj.getX1() + dx );
			this.dObj.setY1( dObj.getY1() + dy );
			break;
		case 2:
			this.dObj.setX2( dObj.getX2() + dx );
			this.dObj.setY2( dObj.getY2() + dy );
			break;
		case 3:
			this.dObj.setX3( dObj.getX3() + dx );
			this.dObj.setY3( dObj.getY3() + dy );
			break;
		case 4:
			this.dObj.setX4( dObj.getX4() + dx );
			this.dObj.setY4( dObj.getY4() + dy );
			break;
		}
	}
	
	
	public void DrawControlPoints(Graphics g)
	{
		this.DrawControlPoint(dObj.getX1(), dObj.getY1(), g);
		this.DrawControlPoint(dObj.getX2(), dObj.getY2(), g);
		this.DrawControlPoint(dObj.getX3(), dObj.getY3(), g);
		this.DrawControlPoint(dObj.getX4(), dObj.getY4(), g);
	}
	
	public int calcControlPoint(Point a)
	{
		Point b = new Point(dObj.getX1(), dObj.getY1());
		if ( distSq(a, b) <= Utils.MIN_DIST_SQ )
			return 1;
		
		b = new Point(dObj.getX2(), dObj.getY2());
		if ( distSq(a, b) <= Utils.MIN_DIST_SQ )
			return 2;
		
		b = new Point(dObj.getX3(), dObj.getY3());
		if ( distSq(a, b) <= Utils.MIN_DIST_SQ )
			return 3;
		
		b = new Point(dObj.getX4(), dObj.getY4());
		if ( distSq(a, b) <= Utils.MIN_DIST_SQ )
			return 4;
	
		
		return 0;
	}

	/*
	 * ******************************************
	 * 				Static Functions
	 * ******************************************
	 * 
	 */
	
	public static ViewObject convert(DrawObject obj, Graphics2D g2)
	{
		if (obj instanceof DrawLine)
			return new ViewLine(obj, g2);
		
		else if (obj instanceof DrawRect)
			return new ViewRect(obj, g2);
		
		else if (obj instanceof DrawEllipse)
			return new ViewEllipse(obj, g2);
		
		else if (obj instanceof DrawBezier)
			return new ViewBezier(obj, g2);
		
		else if (obj instanceof DrawText)
			return new ViewText(obj, g2);
		
		return null;
	}

	public static ViewObject convert(DrawObject obj, Graphics g)
	{
		Graphics2D g2 = (Graphics2D)g;
		return ViewObject.convert(obj, g2);
	}


	
}
